﻿using System.Threading;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using XcoAppSpaces.Core.Ports.Utils;

namespace test.XcoAppSpaces.Core.Ports
{
	[TestFixture]
	public class AsyncReaderWriterLockTests
	{
		private List<string> sequence;
		
		[Test]
		public void ReaderWriterTest()
		{
			//Arrange
			var l = new AsyncReaderWriterLock();
			sequence = new List<string>();

			//Act
			Task.Factory.StartNew(() => Writer(l, 1));
			Task.Factory.StartNew(() => Writer(l, 2));
			Task.Factory.StartNew(() => Reader(l, 1));
			Task.Factory.StartNew(() => Reader(l, 2));
			Task.Factory.StartNew(() => Reader(l, 3));

			//Assert
			Assert.IsTrue(SpinWait.SpinUntil(() => sequence.Count == 10, 3000));
			CheckThatThereIsNothingBetweenWriterStartAndEnd(1);
			CheckThatThereIsNothingBetweenWriterStartAndEnd(2);
		}


		private void CheckThatThereIsNothingBetweenWriterStartAndEnd(int writerId)
		{
			var startIdx = sequence.IndexOf("start writer " + writerId);
			var endIdx = sequence.IndexOf("end writer " + writerId);
			Assert.That(endIdx, Is.EqualTo(startIdx + 1));
		}

		private async void Reader(AsyncReaderWriterLock l, int num)
		{
			using (await l.ReaderLockAsync())
			{
				lock (sequence)
					sequence.Add("start reader " + num);
				Console.WriteLine("start reader " + num);
				await Task.Delay(500);
				lock (sequence)
					sequence.Add("end reader " + num);
				Console.WriteLine("end reader " + num);
			}
		}

		private async void Writer(AsyncReaderWriterLock l, int num)
		{
			using (await l.WriterLockAsync())
			{
				sequence.Add("start writer " + num);
				Console.WriteLine("start writer " + num);
				await Task.Delay(500);
				sequence.Add("end writer " + num);
				Console.WriteLine("end writer " + num);
			}
		}
	}
}
